// Copyright (c) 2016, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:kernel/ast.dart' as ir;
import 'package:kernel/type_environment.dart' as ir;

import '../common.dart';
import '../common/names.dart';
import '../common/elements.dart';
import '../constants/values.dart';
import '../elements/entities.dart';
import '../elements/types.dart';
import '../ir/constants.dart';
import '../ir/impact.dart';
import '../ir/impact_data.dart';
import '../ir/runtime_type_analysis.dart';
import '../ir/visitors.dart';
import '../js_backend/annotations.dart';
import '../js_backend/backend_impact.dart';
import '../js_backend/backend_usage.dart';
import '../js_backend/custom_elements_analysis.dart';
import '../js_backend/native_data.dart';
import '../js_backend/runtime_types_resolution.dart';
import '../js_model/elements.dart';
import '../native/behavior.dart';
import '../native/enqueue.dart';
import '../universe/call_structure.dart';
import '../universe/feature.dart';
import '../universe/selector.dart';
import '../universe/use.dart';
import '../universe/world_builder.dart';
import '../universe/world_impact.dart';
import 'element_map.dart';

/// [ImpactRegistry] that converts kernel based impact data to world impact
/// object based on the K model.
class KernelImpactConverter implements ImpactRegistry {
  final KernelToElementMap elementMap;
  final DiagnosticReporter reporter;
  final MemberEntity currentMember;
  final ConstantValuefier _constantValuefier;
  final ir.StaticTypeContext staticTypeContext;
  final BackendImpacts _impacts;
  final NativeResolutionEnqueuer _nativeResolutionEnqueuer;
  final BackendUsageBuilder _backendUsageBuilder;
  final CustomElementsResolutionAnalysis _customElementsResolutionAnalysis;
  final RuntimeTypesNeedBuilder _rtiNeedBuilder;
  final AnnotationsData _annotationsData;
  WorldImpactBuilder? _impactBuilder;
  WorldImpactBuilder get impactBuilder => _impactBuilder!;

  KernelImpactConverter(
    this.elementMap,
    this.currentMember,
    this.reporter,
    this._constantValuefier,
    this.staticTypeContext,
    this._impacts,
    this._nativeResolutionEnqueuer,
    this._backendUsageBuilder,
    this._customElementsResolutionAnalysis,
    this._rtiNeedBuilder,
    this._annotationsData,
  );

  ir.TypeEnvironment get typeEnvironment => elementMap.typeEnvironment;

  CommonElements get commonElements => elementMap.commonElements;

  NativeBasicData get _nativeBasicData => elementMap.nativeBasicData;

  ElementEnvironment get elementEnvironment => elementMap.elementEnvironment;

  DartTypes get dartTypes => commonElements.dartTypes;

  String typeToString(DartType type) => type.toStructuredText(dartTypes);

  Object? _computeReceiverConstraint(ir.DartType receiverType) {
    if (receiverType is ir.InterfaceType) {
      return defaultReceiverClass(
        commonElements,
        _nativeBasicData,
        elementMap.getClass(receiverType.classNode),
      );
    } else if (receiverType is ir.NullType) {
      return elementMap.getClass(typeEnvironment.coreTypes.deprecatedNullClass);
    }
    return null;
  }

  void registerBackendImpact(BackendImpact impact) {
    impact.registerImpact(impactBuilder, elementEnvironment);
    _backendUsageBuilder.processBackendImpact(impact);
  }

  void registerNativeImpact(NativeBehavior behavior) {
    _nativeResolutionEnqueuer.registerNativeBehavior(
      impactBuilder,
      behavior,
      impactBuilder,
    );
  }

  // TODO(johnniwinther): Maybe split this into [onAssertType] and [onTestType].
  void onIsCheck(DartType type) {
    registerBackendImpact(_impacts.typeCheck);
    var typeWithoutNullability = type.withoutNullability;
    if (!dartTypes.treatAsRawType(typeWithoutNullability) ||
        typeWithoutNullability.containsTypeVariables ||
        typeWithoutNullability is FunctionType) {
      registerBackendImpact(_impacts.genericTypeCheck);
      if (typeWithoutNullability is TypeVariableType) {
        registerBackendImpact(_impacts.typeVariableTypeCheck);
      }
    }
    if (typeWithoutNullability is FunctionType) {
      registerBackendImpact(_impacts.functionTypeCheck);
    }
    if (typeWithoutNullability is InterfaceType &&
        _nativeBasicData.isNativeClass(typeWithoutNullability.element)) {
      registerBackendImpact(_impacts.nativeTypeCheck);
    }
    if (typeWithoutNullability is FutureOrType) {
      registerBackendImpact(_impacts.futureOrTypeCheck);
    }
  }

  @override
  void registerParameterCheck(ir.DartType irType) {
    DartType type = elementMap.getDartType(irType);
    if (type is! DynamicType) {
      impactBuilder.registerTypeUse(TypeUse.parameterCheck(type));
      if (_annotationsData.getParameterCheckPolicy(currentMember).isEmitted) {
        onIsCheck(type);
      }
    }
  }

  List<DartType>? _getTypeArguments(List<ir.DartType> types) {
    if (types.isEmpty) return null;
    return types.map(elementMap.getDartType).toList();
  }

  @override
  void registerLazyField() {
    registerBackendImpact(_impacts.lazyField);
  }

  @override
  void registerFieldNode(ir.Field field) {
    if (field.isInstanceMember &&
        _nativeBasicData.isNativeClass(
          elementMap.getClass(field.enclosingClass!),
        )) {
      MemberEntity member = elementMap.getMember(field);
      // TODO(johnniwinther): NativeDataBuilder already has the native behavior
      // at this point. Use that instead.
      bool isJsInterop = _nativeBasicData.isJsInteropMember(member);
      Iterable<ConstantValue> metadata = elementMap.elementEnvironment
          .getMemberMetadata(member as JMember);
      Iterable<String> createsAnnotations = getCreatesAnnotations(
        dartTypes,
        reporter,
        commonElements,
        metadata,
      );
      Iterable<String> returnsAnnotations = getReturnsAnnotations(
        dartTypes,
        reporter,
        commonElements,
        metadata,
      );
      registerNativeImpact(
        elementMap.getNativeBehaviorForFieldLoad(
          field,
          createsAnnotations,
          returnsAnnotations,
          isJsInterop: isJsInterop,
        ),
      );
      registerNativeImpact(elementMap.getNativeBehaviorForFieldStore(field));
    }
  }

  @override
  void registerExternalConstructorNode(ir.Constructor constructor) {
    MemberEntity member = elementMap.getMember(constructor);
    if (constructor.isExternal && !commonElements.isForeignHelper(member)) {
      // TODO(johnniwinther): NativeDataBuilder already has the native behavior
      // at this point. Use that instead.
      bool isJsInterop = _nativeBasicData.isJsInteropMember(member);
      Iterable<ConstantValue> metadata = elementMap.elementEnvironment
          .getMemberMetadata(member as JMember);
      Iterable<String> createsAnnotations = getCreatesAnnotations(
        dartTypes,
        reporter,
        commonElements,
        metadata,
      );
      Iterable<String> returnsAnnotations = getReturnsAnnotations(
        dartTypes,
        reporter,
        commonElements,
        metadata,
      );
      registerNativeImpact(
        elementMap.getNativeBehaviorForMethod(
          constructor,
          createsAnnotations,
          returnsAnnotations,
          isJsInterop: isJsInterop,
        ),
      );
    }
  }

  @override
  void registerSyncStar(ir.DartType elementType) {
    registerBackendImpact(_impacts.syncStarBody);
    impactBuilder.registerStaticUse(
      StaticUse.staticInvoke(
        commonElements.syncStarIterableFactory,
        CallStructure.unnamed(1, 1),
        <DartType>[elementMap.getDartType(elementType)],
      ),
    );
  }

  @override
  void registerAsync(ir.DartType elementType) {
    registerBackendImpact(_impacts.asyncBody);
    impactBuilder.registerStaticUse(
      StaticUse.staticInvoke(
        commonElements.asyncAwaitCompleterFactory,
        CallStructure.unnamed(0, 1),
        <DartType>[elementMap.getDartType(elementType)],
      ),
    );
  }

  @override
  void registerAsyncStar(ir.DartType elementType) {
    registerBackendImpact(_impacts.asyncStarBody);
    impactBuilder.registerStaticUse(
      StaticUse.staticInvoke(
        commonElements.asyncStarStreamControllerFactory,
        CallStructure.unnamed(1, 1),
        <DartType>[elementMap.getDartType(elementType)],
      ),
    );
  }

  @override
  void registerExternalProcedureNode(ir.Procedure procedure) {
    MemberEntity member = elementMap.getMember(procedure);
    if (procedure.isExternal && !commonElements.isForeignHelper(member)) {
      // TODO(johnniwinther): NativeDataBuilder already has the native behavior
      // at this point. Use that instead.
      bool isJsInterop = _nativeBasicData.isJsInteropMember(member);
      Iterable<ConstantValue> metadata = elementMap.elementEnvironment
          .getMemberMetadata(member as JMember);
      Iterable<String> createsAnnotations = getCreatesAnnotations(
        dartTypes,
        reporter,
        commonElements,
        metadata,
      );
      Iterable<String> returnsAnnotations = getReturnsAnnotations(
        dartTypes,
        reporter,
        commonElements,
        metadata,
      );
      registerNativeImpact(
        elementMap.getNativeBehaviorForMethod(
          procedure,
          createsAnnotations,
          returnsAnnotations,
          isJsInterop: isJsInterop,
        ),
      );
    }
  }

  @override
  void registerIntLiteral() {
    registerBackendImpact(_impacts.intLiteral);
  }

  @override
  void registerDoubleLiteral() {
    registerBackendImpact(_impacts.doubleLiteral);
  }

  @override
  void registerBoolLiteral() {
    registerBackendImpact(_impacts.boolLiteral);
  }

  @override
  void registerStringLiteral() {
    registerBackendImpact(_impacts.stringLiteral);
  }

  @override
  void registerSymbolLiteral() {
    registerBackendImpact(_impacts.constSymbol);
  }

  @override
  void registerNullLiteral() {
    registerBackendImpact(_impacts.nullLiteral);
  }

  @override
  void registerListLiteral(
    ir.DartType elementType, {
    required bool isConst,
    required bool isEmpty,
  }) {
    // TODO(johnniwinther): Use the [isConstant] and [isEmpty] property when
    // factory constructors are registered directly.
    impactBuilder.registerTypeUse(
      TypeUse.instantiation(
        commonElements.listType(elementMap.getDartType(elementType)),
      ),
    );
  }

  @override
  void registerSetLiteral(
    ir.DartType elementType, {
    required bool isConst,
    required bool isEmpty,
  }) {
    // TODO(johnniwinther): Use the [isEmpty] property when factory
    // constructors are registered directly.
    if (isConst) {
      registerBackendImpact(_impacts.constantSetLiteral);
    } else {
      impactBuilder.registerTypeUse(
        TypeUse.instantiation(
          commonElements.setType(elementMap.getDartType(elementType)),
        ),
      );
    }
  }

  @override
  void registerMapLiteral(
    ir.DartType keyType,
    ir.DartType valueType, {
    required bool isConst,
    required bool isEmpty,
  }) {
    // TODO(johnniwinther): Use the [isEmpty] property when factory
    // constructors are registered directly.
    if (isConst) {
      registerBackendImpact(_impacts.constantMapLiteral);
    } else {
      impactBuilder.registerTypeUse(
        TypeUse.instantiation(
          commonElements.mapType(
            elementMap.getDartType(keyType),
            elementMap.getDartType(valueType),
          ),
        ),
      );
    }
  }

  @override
  void registerRecordLiteral(
    ir.RecordType recordType, {
    required bool isConst,
  }) {
    registerBackendImpact(_impacts.recordInstantiation);
    final type = elementMap.getDartType(recordType) as RecordType;
    impactBuilder.registerTypeUse(TypeUse.recordInstantiation(type));
  }

  @override
  void registerNew(
    ir.Member target,
    ir.InterfaceType type,
    int positionalArguments,
    List<String> namedArguments,
    List<ir.DartType> typeArguments,
    ir.LibraryDependency? import, {
    required bool isConst,
  }) {
    ConstructorEntity constructor = elementMap.getConstructor(target);
    CallStructure callStructure = CallStructure(
      positionalArguments + namedArguments.length,
      namedArguments,
      typeArguments.length,
    );
    ImportEntity? deferredImport = elementMap.getImport(import);
    impactBuilder.registerStaticUse(
      isConst
          ? StaticUse.constConstructorInvoke(
            constructor,
            callStructure,
            elementMap.getInterfaceType(type),
            deferredImport,
          )
          : StaticUse.typedConstructorInvoke(
            constructor,
            callStructure,
            elementMap.getInterfaceType(type),
            deferredImport,
          ),
    );
    if (type.typeArguments.any((ir.DartType type) => type is! ir.DynamicType)) {
      registerBackendImpact(_impacts.typeVariableBoundCheck);
    }

    if (target.isExternal &&
        constructor.isFromEnvironmentConstructor &&
        !isConst) {
      registerBackendImpact(_impacts.throwUnsupportedError);
      // We need to register the external constructor as live below, so don't
      // return here.
    }
  }

  @override
  void registerConstInstantiation(
    ir.Class cls,
    List<ir.DartType> typeArguments,
    ir.LibraryDependency? import,
  ) {
    ImportEntity? deferredImport = elementMap.getImport(import);
    InterfaceType type = elementMap.createInterfaceType(cls, typeArguments);
    impactBuilder.registerTypeUse(
      TypeUse.constInstantiation(type, deferredImport),
    );
  }

  @override
  void registerConstSymbolConstructorInvocationNode() {
    registerBackendImpact(_impacts.constSymbol);
  }

  @override
  void registerSuperInitializer(
    ir.Constructor source,
    ir.Constructor target,
    int positionalArguments,
    List<String> namedArguments,
    List<ir.DartType> typeArguments,
  ) {
    // TODO(johnniwinther): Maybe rewrite `node.target` to point to a
    // synthesized unnamed mixin constructor when needed. This would require us
    // to consider impact building a required pre-step for inference and
    // ssa-building.
    ConstructorEntity constructor = elementMap.getSuperConstructor(
      source,
      target,
    );
    impactBuilder.registerStaticUse(
      StaticUse.superConstructorInvoke(
        constructor,
        CallStructure(
          positionalArguments + namedArguments.length,
          namedArguments,
          typeArguments.length,
        ),
      ),
    );
  }

  @override
  void registerStaticInvocation(
    ir.Procedure procedure,
    int positionalArguments,
    List<String> namedArguments,
    List<ir.DartType> typeArguments,
    ir.LibraryDependency? import,
  ) {
    FunctionEntity target = elementMap.getMethod(procedure);
    CallStructure callStructure = CallStructure(
      positionalArguments + namedArguments.length,
      namedArguments,
      typeArguments.length,
    );
    List<DartType>? dartTypeArguments = _getTypeArguments(typeArguments);
    if (commonElements.isExtractTypeArguments(target)) {
      _handleExtractTypeArguments(target, dartTypeArguments!, callStructure);
      return;
    } else {
      ImportEntity? deferredImport = elementMap.getImport(import);
      impactBuilder.registerStaticUse(
        StaticUse.staticInvoke(
          target,
          callStructure,
          dartTypeArguments,
          deferredImport,
        ),
      );
    }
  }

  @override
  void registerForeignStaticInvocationNode(ir.StaticInvocation node) {
    switch (elementMap.getForeignKind(node)) {
      case ForeignKind.js:
        registerNativeImpact(elementMap.getNativeBehaviorForJsCall(node));
        break;
      case ForeignKind.jsBuiltin:
        registerNativeImpact(
          elementMap.getNativeBehaviorForJsBuiltinCall(node),
        );
        break;
      case ForeignKind.jsEmbeddedGlobal:
        registerNativeImpact(
          elementMap.getNativeBehaviorForJsEmbeddedGlobalCall(node),
        );
        break;
      case ForeignKind.jsInterceptorConstant:
        InterfaceType? type = elementMap.getInterfaceTypeForJsInterceptorCall(
          node,
        );
        if (type != null) {
          impactBuilder.registerTypeUse(TypeUse.instantiation(type));
        }
        break;
      case ForeignKind.none:
        break;
    }
  }

  void _handleExtractTypeArguments(
    FunctionEntity target,
    List<DartType> typeArguments,
    CallStructure callStructure,
  ) {
    // extractTypeArguments<Map>(obj, fn) has additional impacts:
    //
    //   1. All classes implementing Map need to carry type arguments (similar
    //      to checking `o is Map<K, V>`).
    //
    //   2. There is an invocation of fn with some number of type arguments.
    //
    impactBuilder.registerStaticUse(
      StaticUse.staticInvoke(target, callStructure, typeArguments),
    );

    if (typeArguments.length != 1) return;
    DartType matchedType = typeArguments.single;

    if (matchedType is! InterfaceType) return;
    InterfaceType interfaceType = matchedType;
    ClassEntity cls = interfaceType.element;
    InterfaceType thisType = elementMap.elementEnvironment.getThisType(cls);
    _registerIsCheckInternal(thisType);

    Selector selector = Selector.callClosure(
      0,
      const <String>[],
      thisType.typeArguments.length,
    );
    impactBuilder.registerDynamicUse(
      DynamicUse(selector, null, thisType.typeArguments),
    );
  }

  @override
  void registerStaticTearOff(
    ir.Procedure procedure,
    ir.LibraryDependency? import,
  ) {
    impactBuilder.registerStaticUse(
      StaticUse.staticTearOff(
        elementMap.getMethod(procedure),
        elementMap.getImport(import),
      ),
    );
  }

  @override
  void registerWeakStaticTearOff(
    ir.Procedure procedure,
    ir.LibraryDependency? import,
  ) {
    impactBuilder.registerStaticUse(
      StaticUse.weakStaticTearOff(
        elementMap.getMethod(procedure),
        elementMap.getImport(import),
      ),
    );
  }

  @override
  void registerStaticGet(ir.Member member, ir.LibraryDependency? import) {
    impactBuilder.registerStaticUse(
      StaticUse.staticGet(
        elementMap.getMember(member),
        elementMap.getImport(import),
      ),
    );
  }

  @override
  void registerStaticSet(ir.Member member, ir.LibraryDependency? import) {
    impactBuilder.registerStaticUse(
      StaticUse.staticSet(
        elementMap.getMember(member),
        elementMap.getImport(import),
      ),
    );
  }

  @override
  void registerSuperInvocation(
    ir.Member target,
    int positionalArguments,
    List<String> namedArguments,
    List<ir.DartType> typeArguments,
  ) {
    FunctionEntity method = elementMap.getMember(target) as FunctionEntity;
    List<DartType>? dartTypeArguments = _getTypeArguments(typeArguments);
    impactBuilder.registerStaticUse(
      StaticUse.superInvoke(
        method,
        CallStructure(
          positionalArguments + namedArguments.length,
          namedArguments,
          typeArguments.length,
        ),
        dartTypeArguments,
      ),
    );
  }

  @override
  void registerSuperGet(ir.Member target) {
    MemberEntity member = elementMap.getMember(target);
    if (member.isFunction) {
      impactBuilder.registerStaticUse(
        StaticUse.superTearOff(member as FunctionEntity),
      );
    } else {
      impactBuilder.registerStaticUse(StaticUse.superGet(member));
    }
  }

  @override
  void registerSuperSet(ir.Member target) {
    MemberEntity member = elementMap.getMember(target);
    if (member is FieldEntity) {
      impactBuilder.registerStaticUse(StaticUse.superFieldSet(member));
    } else {
      impactBuilder.registerStaticUse(
        StaticUse.superSetterSet(member as FunctionEntity),
      );
    }
  }

  @override
  void registerLocalFunctionInvocation(
    ir.FunctionDeclaration localFunction,
    int positionalArguments,
    List<String> namedArguments,
    List<ir.DartType> typeArguments,
  ) {
    CallStructure callStructure = CallStructure(
      positionalArguments + namedArguments.length,
      namedArguments,
      typeArguments.length,
    );
    List<DartType>? dartTypeArguments = _getTypeArguments(typeArguments);
    // Invocation of a local function. No need for dynamic use, but
    // we need to track the type arguments.
    impactBuilder.registerStaticUse(
      StaticUse.closureCall(
        elementMap.getLocalFunction(localFunction),
        callStructure,
        dartTypeArguments,
      ),
    );
    // TODO(johnniwinther): Yet, alas, we need the dynamic use for now. Remove
    // this when kernel adds an `isFunctionCall` flag to
    // [ir.MethodInvocation].
    impactBuilder.registerDynamicUse(
      DynamicUse(callStructure.callSelector, null, dartTypeArguments),
    );
  }

  @override
  void registerDynamicInvocation(
    ir.DartType receiverType,
    ir.Name name,
    int positionalArguments,
    List<String> namedArguments,
    List<ir.DartType> typeArguments,
  ) {
    Selector selector = elementMap.getInvocationSelector(
      name,
      positionalArguments,
      namedArguments,
      typeArguments.length,
    );
    List<DartType>? dartTypeArguments = _getTypeArguments(typeArguments);
    impactBuilder.registerDynamicUse(
      DynamicUse(
        selector,
        _computeReceiverConstraint(receiverType),
        dartTypeArguments,
      ),
    );
  }

  @override
  void registerFunctionInvocation(
    ir.DartType receiverType,
    int positionalArguments,
    List<String> namedArguments,
    List<ir.DartType> typeArguments,
  ) {
    CallStructure callStructure = CallStructure(
      positionalArguments + namedArguments.length,
      namedArguments,
      typeArguments.length,
    );
    List<DartType>? dartTypeArguments = _getTypeArguments(typeArguments);
    impactBuilder.registerDynamicUse(
      DynamicUse(
        callStructure.callSelector,
        _computeReceiverConstraint(receiverType),
        dartTypeArguments,
      ),
    );
  }

  @override
  void registerInstanceInvocation(
    ir.DartType receiverType,
    ir.Member target,
    int positionalArguments,
    List<String> namedArguments,
    List<ir.DartType> typeArguments,
  ) {
    List<DartType>? dartTypeArguments = _getTypeArguments(typeArguments);
    impactBuilder.registerDynamicUse(
      DynamicUse(
        elementMap.getInvocationSelector(
          target.name,
          positionalArguments,
          namedArguments,
          typeArguments.length,
        ),
        _computeReceiverConstraint(receiverType),
        dartTypeArguments,
      ),
    );
  }

  @override
  void registerDynamicGet(ir.DartType receiverType, ir.Name name) {
    impactBuilder.registerDynamicUse(
      DynamicUse(
        Selector.getter(elementMap.getName(name)),
        _computeReceiverConstraint(receiverType),
        const <DartType>[],
      ),
    );
  }

  @override
  void registerInstanceGet(ir.DartType receiverType, ir.Member target) {
    impactBuilder.registerDynamicUse(
      DynamicUse(
        Selector.getter(elementMap.getName(target.name)),
        _computeReceiverConstraint(receiverType),
        const <DartType>[],
      ),
    );
  }

  @override
  void registerDynamicSet(ir.DartType receiverType, ir.Name name) {
    impactBuilder.registerDynamicUse(
      DynamicUse(
        Selector.setter(elementMap.getName(name)),
        _computeReceiverConstraint(receiverType),
        const <DartType>[],
      ),
    );
  }

  @override
  void registerInstanceSet(ir.DartType receiverType, ir.Member target) {
    impactBuilder.registerDynamicUse(
      DynamicUse(
        Selector.setter(elementMap.getName(target.name)),
        _computeReceiverConstraint(receiverType),
        const <DartType>[],
      ),
    );
  }

  @override
  void registerRuntimeTypeUse(
    RuntimeTypeUseKind kind,
    ir.DartType receiverType,
    ir.DartType? argumentType,
  ) {
    DartType receiverDartType = elementMap.getDartType(receiverType);
    DartType? argumentDartType =
        argumentType == null ? null : elementMap.getDartType(argumentType);

    // Enable runtime type support if we discover a getter called
    // runtimeType. We have to enable runtime type before hitting the
    // codegen, so that constructors know whether they need to generate code
    // for runtime type.
    _backendUsageBuilder.registerRuntimeTypeUse(
      RuntimeTypeUse(kind, receiverDartType, argumentDartType),
    );
  }

  @override
  void registerAssert({required bool withMessage}) {
    registerBackendImpact(
      withMessage ? _impacts.assertWithMessage : _impacts.assertWithoutMessage,
    );
  }

  @override
  void registerGenericInstantiation(
    ir.FunctionType expressionType,
    List<ir.DartType> typeArguments,
  ) {
    // TODO(johnniwinther): Track which arities are used in instantiation.
    final instantiation = GenericInstantiation(
      elementMap.getDartType(expressionType).withoutNullability as FunctionType,
      typeArguments.map(elementMap.getDartType).toList(),
    );
    registerBackendImpact(
      _impacts.getGenericInstantiation(instantiation.typeArguments.length),
    );
    _rtiNeedBuilder.registerGenericInstantiation(instantiation);
  }

  @override
  void registerStringConcatenation() {
    registerBackendImpact(_impacts.stringInterpolation);
    registerBackendImpact(_impacts.stringJuxtaposition);
  }

  @override
  void registerLocalFunction(covariant ir.LocalFunction node) {
    Local function = elementMap.getLocalFunction(node);
    impactBuilder.registerStaticUse(StaticUse.closure(function));
    registerBackendImpact(_impacts.closure);
    registerBackendImpact(_impacts.computeSignature);
  }

  @override
  void registerLocalWithoutInitializer() {
    impactBuilder.registerTypeUse(
      TypeUse.instantiation(commonElements.nullType),
    );
    registerBackendImpact(_impacts.nullLiteral);
  }

  void _registerIsCheckInternal(DartType type) {
    impactBuilder.registerTypeUse(TypeUse.isCheck(type));
    onIsCheck(type);
  }

  @override
  void registerIsCheck(ir.DartType irType) {
    _registerIsCheckInternal(elementMap.getDartType(irType));
  }

  @override
  void registerImplicitCast(ir.DartType irType) {
    DartType type = elementMap.getDartType(irType);
    impactBuilder.registerTypeUse(TypeUse.implicitCast(type));
    if (_annotationsData
        .getImplicitDowncastCheckPolicy(currentMember)
        .isEmitted) {
      onIsCheck(type);
    }
  }

  @override
  void registerAsCast(ir.DartType irType) {
    DartType type = elementMap.getDartType(irType);
    impactBuilder.registerTypeUse(TypeUse.asCast(type));
    if (_annotationsData.getExplicitCastCheckPolicy(currentMember).isEmitted) {
      onIsCheck(type);
      registerBackendImpact(_impacts.asCheck);
    }
  }

  @override
  void registerThrow() {
    registerBackendImpact(_impacts.throwExpression);
  }

  @override
  void registerSyncForIn(ir.DartType iterableType, ir.DartType iteratorType) {
    Object? receiverConstraint = _computeReceiverConstraint(iteratorType);
    registerBackendImpact(_impacts.syncForIn);
    impactBuilder.registerDynamicUse(
      DynamicUse(Selectors.iterator, receiverConstraint, const []),
    );
    impactBuilder.registerDynamicUse(
      DynamicUse(Selectors.current, receiverConstraint, const []),
    );
    impactBuilder.registerDynamicUse(
      DynamicUse(Selectors.moveNext, receiverConstraint, const []),
    );
  }

  @override
  void registerAsyncForIn(ir.DartType iterableType, ir.DartType iteratorType) {
    Object? receiverConstraint = _computeReceiverConstraint(iteratorType);
    registerBackendImpact(_impacts.asyncForIn);
    impactBuilder.registerDynamicUse(
      DynamicUse(Selectors.cancel, receiverConstraint, const []),
    );
    impactBuilder.registerDynamicUse(
      DynamicUse(Selectors.current, receiverConstraint, const []),
    );
    impactBuilder.registerDynamicUse(
      DynamicUse(Selectors.moveNext, receiverConstraint, const []),
    );
  }

  @override
  void registerCatch() {
    registerBackendImpact(_impacts.catchStatement);
  }

  @override
  void registerStackTrace() {
    registerBackendImpact(_impacts.stackTraceInCatch);
  }

  @override
  void registerCatchType(ir.DartType irType) {
    DartType type = elementMap.getDartType(irType);
    impactBuilder.registerTypeUse(TypeUse.catchType(type));
    onIsCheck(type);
  }

  @override
  void registerTypeLiteral(ir.DartType irType, ir.LibraryDependency? import) {
    ImportEntity? deferredImport = elementMap.getImport(import);
    DartType type = elementMap.getDartType(irType);
    impactBuilder.registerTypeUse(TypeUse.typeLiteral(type, deferredImport));
    _customElementsResolutionAnalysis.registerTypeLiteral(type);
    type.forEachTypeVariable((TypeVariableType variable) {
      _rtiNeedBuilder.registerTypeVariableLiteral(variable);
      registerBackendImpact(_impacts.typeVariableExpression);
    });
    impactBuilder.registerTypeUse(
      TypeUse.instantiation(commonElements.typeType),
    );
    registerBackendImpact(_impacts.typeLiteral);
  }

  @override
  void registerFieldInitialization(ir.Field node) {
    impactBuilder.registerStaticUse(
      StaticUse.fieldInit(elementMap.getField(node)),
    );
  }

  @override
  void registerFieldConstantInitialization(
    ir.Field node,
    ConstantReference constant,
  ) {
    impactBuilder.registerStaticUse(
      StaticUse.fieldConstantInit(
        elementMap.getField(node),
        _constantValuefier.visitConstant(constant.constant),
      ),
    );
  }

  @override
  void registerRedirectingInitializer(
    ir.Constructor constructor,
    int positionalArguments,
    List<String> namedArguments,
    List<ir.DartType> typeArguments,
  ) {
    ConstructorEntity target = elementMap.getConstructor(constructor);
    impactBuilder.registerStaticUse(
      StaticUse.superConstructorInvoke(
        target,
        CallStructure(
          positionalArguments + namedArguments.length,
          namedArguments,
          typeArguments.length,
        ),
      ),
    );
  }

  @override
  void registerLoadLibrary() {
    impactBuilder.registerStaticUse(
      StaticUse.staticInvoke(
        commonElements.loadDeferredLibrary,
        CallStructure.oneArg,
      ),
    );
    registerBackendImpact(_impacts.loadLibrary);
  }

  /// Converts a [ImpactData] object based on kernel to the corresponding
  /// [WorldImpact] based on the K model.
  WorldImpact convert(ImpactData impactData) {
    final oldBuilder = _impactBuilder;
    final newBuilder = _impactBuilder = WorldImpactBuilderImpl(currentMember);
    impactData.apply(this);
    _impactBuilder = oldBuilder;
    return newBuilder;
  }

  @override
  void registerConditionalImpact(ConditionalImpactData impact) {
    final conditionalUse = ConditionalUse.withReplacement(
      impact: convert(impact.impactData),
      replacementImpact: convert(impact.replacementImpactData),
      originalConditions:
          impact.originalConditions.map(elementMap.getMember).toList(),
      original: impact.original,
      replacement: impact.replacement,
    );
    impactBuilder.registerConditionalUse(conditionalUse);
  }
}
